package com.nulldev.util.web.nTube.pages.streams;

import static com.nulldev.util.web.nTube.pages.Urlizer.urlizer;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.codecs.uniapi.UniBase64;
import com.nulldev.util.concurrency.threadIt.v4.emapi.IExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.Arrays.arrays.ConcurrentList;
import com.nulldev.util.data.Arrays.arrays.MultiComparator;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.data.CompressionAPI.thirdparty.airlift.snappy.SnappyCompressor;
import com.nulldev.util.data.CompressionAPI.thirdparty.heatshrink.HeatShrinkEncoder;
import com.nulldev.util.internal.backport.arrays.BackportList;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpServer3.ReadRequest;
import com.nulldev.util.web.HttpServer3.extensions.sse.SSEPage;
import com.nulldev.util.web.HttpServer3.extensions.sse.SSESession;
import com.nulldev.util.web.HttpServer3.extensions.sse.event.SSEvent;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractorConstants;
import com.nulldev.util.web.YouTubeExtractor.clarifications.Clarification;
import com.nulldev.util.web.YouTubeExtractor.comments.YTComments.YTCommentInitData;
import com.nulldev.util.web.YouTubeExtractor.endscreen.EndscreenEntry;
import com.nulldev.util.web.YouTubeExtractor.formats.AudioCodec;
import com.nulldev.util.web.YouTubeExtractor.formats.Format;
import com.nulldev.util.web.YouTubeExtractor.formats.VideoCodec;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeStoryboardParser;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeStoryboardParser.StoryboardSection;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeStoryboardParser.YouTubeStoryboard;
import com.nulldev.util.web.YouTubeExtractor.items.YouTubeVideoHeatmap;
import com.nulldev.util.web.YouTubeExtractor.metadata.YouTubeMetadata;
import com.nulldev.util.web.YouTubeExtractor.metadata.YouTubeMetadata.DRMMetadata.DRMFamily;
import com.nulldev.util.web.YouTubeExtractor.video.ExtractedVideo;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo.FormatType;
import com.nulldev.util.web.nTube.NTubeServer;
import com.nulldev.util.web.nTube.pages.GetVideoAsyncPage;
import com.nulldev.util.web.nTube.pages.GetVideoAsyncPage.TelemetryData;
import com.nulldev.util.web.nTube.pages.GetVideoAsyncPage.VideoRequestProfile;
import com.nulldev.util.web.nTube.util.VideoPageUtil;

public class ClientVideoRequestStream extends SSEPage {

	public ClientVideoRequestStream() {
		super("/api/streams/video_requests");
	}

	private static final Logger log = LoggerFactory.getLogger();
	private static final double CVRS_VIDEO_API = 0.02;
	private final Map<String, SSESession> sessions = FastMaps.map();
	private static final String[] SUPPORTED_COMPRESSION_TYPES =
		{ "standard", "heatshrink", "snappy" };

	private static final String[] SUPPORTED_ENCODING_AS =
		{ "string", "base64"
				// "base62"
				// "base32"
		};

	public static enum CompressionType {
		STANDARD,
		HEATSHRINK,
		SNAPPY
	}

	@Override
	public void sessionConnected(final SSESession sseSession, final ReadRequest rr, final String url) {
		final String userID = StringsUtil.generateSecure(32);
		this.sessions.put(userID, sseSession);
		try {
			final UniJsonValue dataObj = UniJsonValue.object();
			dataObj.add("id", userID);
			dataObj.add("served_api", CVRS_VIDEO_API);
			final UniJsonValue compressions = UniJsonValue.array();
			compressions.addAll(SUPPORTED_COMPRESSION_TYPES);
			dataObj.add("supportedCompressionTypes", compressions);
			dataObj.add("preferredCompressionType", "standard");
			final UniJsonValue encodings = UniJsonValue.array();
			encodings.addAll(SUPPORTED_ENCODING_AS);
			dataObj.add("supportedEncodingTypes", compressions);
			dataObj.add("preferredEncodingType", "string");
			sseSession.sendEvent(SSEvent.bldr().retry(100).event("stream_init").data(dataObj.toString()).build());
		} catch (IOException e) {
			log.error(e);
		}
	}

	public void sendTo(final ExtractedVideo video, final VideoRequestProfile vrp, final TelemetryData td, final String id, final boolean isLegacyBackend)
			throws IOException {
		this.sendTo(video, vrp, td, this.sessions.get(id), isLegacyBackend);
	}

	private static final IExecutorManager td = ClientVideoRequestPage.td;

	private void appendMetadata(final UniJsonValue out, final ExtractedVideo result, final boolean isLegacyBackend) {
		if (result == null || out == null || out.isNull())
			return;
		final YouTubeMetadata metadata = result.getMetadata();
		if (metadata == null)
			return;
		final UniJsonValue meta = UniJsonValue.object(23);
		meta.add("title", metadata.getTitle());
		meta.add("duration", metadata.getDuration());
		meta.add("rating", metadata.getRating());
		try {
			meta.add("percentRating", (metadata.getRating() / 5 * 100));
		} catch (Exception e) {
			log.error("Failed to generate percentRating.", e);
		}
		final UniJsonValue tags = UniJsonValue.array(metadata.getTags().length);
		for (final String tag : metadata.getTags()) {
			tags.add(tag);
		}
		meta.add("tags", tags);
		meta.add("author", metadata.getAuthor());
		meta.add("authorID", metadata.getAuthorID());
		meta.add("views", metadata.getViewCount());
		meta.add("description", metadata.getEnhancedDescription(true));
		meta.add("limitedExtractionData", metadata.limitedExtraction());
		meta.add("isLiveStream", metadata.isLiveStream());
		meta.add("isDVR", metadata.isDVRStream());
		meta.add("isLowLatencyStream", metadata.isLowLatencyStream());
		if (metadata.hasRawStoryboardURL()) {
			final UniJsonValue storyboard = UniJsonValue.object();
			storyboard.add("raw", metadata.getRawStoryboardURL());
			final YouTubeStoryboard sb = YouTubeStoryboardParser.parse(metadata.getRawStoryboardURL());
			storyboard.add("best", sb.getBestSectionSize());
			storyboard.add("sectionCount", sb.getSectionCount());
			final UniJsonValue sections = UniJsonValue.array();
			for (final StoryboardSection section : sb.getSections()) {
				final UniJsonValue sec = UniJsonValue.object();
				sec.add("columns", section.columns);
				sec.add("duration", section.duration);
				sec.add("height", section.height);
				sec.add("rows", section.rows);
				sec.add("width", section.width);
				sec.add("imageName", section.imageName);
				sec.add("quality", section.quality);
				sec.add("signatureHash", section.signatureHash);
				sections.add(sec);
			}
			storyboard.add("sections", sections);
			meta.add("storyboard", storyboard);
		}
		if (metadata.hasRawCaptionsURL()) {
			meta.add("raw_captions", metadata.getRawCaptionsURL());
		}
		if (metadata.hasThumbnailLevel()) {
			meta.add("thumbnailLevel", metadata.getThumbnailLevel());
		}
		if (metadata.hasContentStartTime()) {
			meta.add("contentStartTime", metadata.getContentStartTime());
		}
		if (metadata.is360()) {
			meta.add("is360", metadata.is360());
		}
		if (metadata.hasPlaybackMode()) {
			meta.add("playbackMode", metadata.getPlaybackMode().id);
		}
		if (metadata.hasLocalizedTitle()) {
			meta.add("localizedTitle", metadata.getLocalizedTitle());
		}
		if (result.hasCommentData()) {
			final UniJsonValue commentData = UniJsonValue.object();
			final YTCommentInitData ytc = result.getCommentData();
			commentData.add("clientName", ytc.getClientName());
			commentData.add("clientVersion", ytc.getClientVersion());
			commentData.add("token", ytc.getCommentToken());
			commentData.add("videoID", ytc.getVideoID());
			commentData.add("cookiePayload", ytc.getCookiePayload());
			commentData.add("xsrfToken", ytc.getXSRFToken());
			if (result.hasClientType())
				commentData.add("clientType", result.getClientType().getID());
			if (isLegacyBackend)
				commentData.add("legacy", true);
			meta.add("comments", commentData);
		}
		if (result.hasChatPayload()) {
			final UniJsonValue chatData = UniJsonValue.object();
			chatData.add("cont", result.getChatPayload());
			if (Arguments.hasArgument("--NTubeServer.exposeIPs")) {
				chatData.add("rawURL", "https://www.youtube.com/live_chat?continuation=" + result.getChatPayload());
			}
			chatData.add("url", urlizer("https://www.youtube.com/live_chat?continuation=" + result.getChatPayload()) + "&ytlive");
			meta.add("chat", chatData);
		}
		{
			final UniJsonValue ed = UniJsonValue.object();
			ed.add("familyFriendly", metadata.getExtendedMetadata().isFamilyFriendly);
			ed.add("publishDate", metadata.getExtendedMetadata().publishDate);
			ed.add("uploadDate", metadata.getExtendedMetadata().uploadDate);
			ed.add("rawLoudnessDb", metadata.getExtendedMetadata().loudnessDbRaw);
			ed.add("perceptualLoudnessDb", metadata.getExtendedMetadata().perceptualLoudnessDb);
			meta.add("extended", ed);
		}
		if (metadata.hasEndscreenEntries()) {
			final UniJsonValue endscreen = UniJsonValue.array(metadata.getEndscreenEntries().size());
			for (final EndscreenEntry e : metadata.getEndscreenEntries()) {
				final UniJsonValue o = UniJsonValue.object();
				o.add("type", e.getType().id);
				o.add("url", e.getURL());
				o.add("video_id", e.getVideoID());
				o.add("text", e.getText());
				o.add("subText", e.getSubText());
				o.add("img", e.getImageURL());
				endscreen.add(o);
			}
			meta.add("endscreen", endscreen);
		}
		if (metadata.hasClarifications()) {
			final UniJsonValue a = UniJsonValue.array(metadata.getClarifications().size());
			for (final Clarification e : metadata.getClarifications()) {
				final UniJsonValue o = UniJsonValue.object();
				o.add("title", e.getTitle());
				o.add("text", e.getText());
				o.add("subText", e.getSubText());
				o.add("url", e.getURL());
				a.add(o);
			}
			meta.add("clarifications", a);
		}
		if (metadata.hasSupertags()) {
			final UniJsonValue a = UniJsonValue.array(metadata.getSupertags().size());
			for (final String tag : metadata.getSupertags()) {
				a.add(tag);
			}
			meta.add("supertags", a);
		}
		if (metadata.hasLikeData()) {
			final UniJsonValue ld = UniJsonValue.object(2);
			ld.add("likes", metadata.getLikeCount());
			ld.add("dislikes", metadata.getDislikeCount());
			meta.add("likeData", ld);
		}
		if (metadata.hasHeatmap()) {
			final Executable<YouTubeVideoHeatmap> heat = metadata.getHeatmap();
			if (!heat.completed())
				try {
					heat.waitFor(200, TimeUnit.MILLISECONDS);
				} catch (Exception ex) {
					log.error("Failed to wait for heatmap!", ex);
				}
			if (heat.completed()) {
				final YouTubeVideoHeatmap heatmap = heat.result();
				if (heatmap != null) {
					final UniJsonValue arr = UniJsonValue.array(heatmap.getSize());
					for (final Object[] data : heatmap.getHeatmap()) {
						if (data == null || data.length == 0)
							continue;
						final UniJsonValue entry = UniJsonValue.object(YouTubeVideoHeatmap.ENTRY_SET_SIZE);
						entry.add("heatmapPointDuration", MathUtil.autoConvertToLong(data[0]));
						entry.add("heatmapPointStartMillis", MathUtil.autoConvertToLong(data[1]));
						entry.add("heatmapIntensity", (float) data[2]);
						entry.add("isPriorityPoint", (boolean) data[3]);
						arr.add(entry);
					}
					meta.add("heatmap", arr);
				}
			}
		}
		out.add("metadata", meta);
	}

	public void sendTo(final ExtractedVideo result, final VideoRequestProfile vrp, final TelemetryData telemetry, final SSESession session,
			final boolean isLegacyBackend) throws IOException {
		this.sendTo(result, vrp, telemetry, CompressionType.STANDARD, session, isLegacyBackend);
	}

	public void sendTo(final ExtractedVideo result, final VideoRequestProfile vrp, final TelemetryData telemetry, final CompressionType compressWith,
			final SSESession session, final boolean isLegacyBackend) throws IOException {
		if (session == null || !session.isOpen()) {
			log.warn("Unknown session: " + session);
			return;
		}
		final UniJsonValue outv = UniJsonValue.object();
		outv.add("type", "CVRS_VIDEO");
		final UniJsonValue out = UniJsonValue.object();
		final UniJsonValue env = UniJsonValue.object();
		env.add("clientVersion", YouTubeExtractorConstants.CLIENT_VERSION);
		env.add("videoAPIVersion", 1.7);
		env.add("videoAPIVersionNS", GetVideoAsyncPage.VIDEO_API_UPCOMING_VER_NS.asVersionString());
		env.add("serverVersion", NTubeServer.nTube_RELEASE);
		out.add("environment", env);
		if (result == null) {
			out.add("error_id", "ERROR_RESULT_NULL");
		} else if (result.hasIDInfo() || !result.getResult().successful()) {
			out.add("error_id", result.getIDInfo());
			if (result.hasYTInfo()) {
				out.add("error_message", result.getYTInfo());
			}
			this.appendMetadata(out, result, isLegacyBackend);
		} else {
			final Executable<Object> metadata_gen = td.asyncSubmit(() -> {
				ClientVideoRequestStream.this.appendMetadata(out, result, isLegacyBackend);
				return null;
			});
			final List<YouTubeVideo> videos = result.videos();
			if (videos == null) {
				log.error("Got a null YouTube video source list, panicing.");
				return;
			}
			log.debug("Got " + videos.size() + " video sources.");
			final List<YouTubeVideo> audioStreams = new ConcurrentList<YouTubeVideo>(); // new FastArrayList<YoutubeVideo>(STRS_ENABLE_FAST_AL);
			final List<YouTubeVideo> videoStreams = new ConcurrentList<YouTubeVideo>();// new FastArrayList<YoutubeVideo>(STRS_ENABLE_FAST_AL);
			final List<YouTubeVideo> combinedStreams = new ConcurrentList<YouTubeVideo>();// new FastArrayList<YoutubeVideo>(STRS_ENABLE_FAST_AL);
			videos.parallelStream().forEach((video) -> {
				if (video == null) {
					log.warn("Got a null YouTubeVideo source. [e=" + video + "]");
				} else if (video.getFormatType() == FormatType.OTF_STREAM || video.getFormatType() == FormatType.DASH_STREAM) {
					log.warn("Skipped processing an unsupported YouTubeVideo source. [e=" + video + "]");
				} else {
					final Format f = video.getFormat();
					if (f == null) {
						log.error("f was null! Skipping. [itag=" + video.getTag() + "]");
					} else {
						if (!f.getAudioCodec().equals(AudioCodec.NONE) && f.getVideoCodec().equals(VideoCodec.NONE)) {
							if (vrp.openCodecsOnly && (!f.getAudioCodec().isOpen())) {
							} else {
								audioStreams.add(video);
							}
						} else if (f.getAudioCodec().equals(AudioCodec.NONE) && !f.getVideoCodec().equals(VideoCodec.NONE)) {
							if (f.isHDR() && vrp.enableHDR) {
								videoStreams.add(video);
							} else if (f.getVideoCodec().equals(VideoCodec.AV1) && !vrp.enableAV1) {
							} else if (ArrayUtils.contains(VideoCodec.FAMILY_VPX, f.getVideoCodec()) && !vrp.allowWebMs) {
							} else if (vrp.openCodecsOnly && !f.getVideoCodec().isOpenCodec()) {
							} else if (!f.isHDR()) {
								videoStreams.add(video);
							}
						} else if (!f.getAudioCodec().equals(AudioCodec.NONE) && !f.getVideoCodec().equals(VideoCodec.NONE)) {
							if (vrp.openCodecsOnly && !f.getVideoCodec().isOpenCodec()) {
							} else {
								combinedStreams.add(video);
							}
						} else {
							log.warn("Skipped video: " + video);
						}
					}
				}
			});
			log.debug("wait::PARALLEL_STREAM_PRE_SORT");
			BackportList.sort(audioStreams, GetVideoAsyncPage.AS_BITRATE_COMP);
			log.debug("wait::PARALLEL_STREAM_SORT[post-audio]");
			BackportList.sort(combinedStreams, GetVideoAsyncPage.CS_HEIGHT_COMP);
			log.debug("wait::PARALLEL_STREAM_SORT[post-comb]");
			MultiComparator.sort(videoStreams, GetVideoAsyncPage.VS_HEIGHT_COMP, GetVideoAsyncPage.VS_FPS_COMP);
			log.debug("wait::PARALLEL_STREAM_SORT[post-video]");
			final Executable<UniJsonValue> asd_gen = td.asyncSubmit(() -> {
				final UniJsonValue audio = UniJsonValue.array();
				for (final YouTubeVideo s : audioStreams) {
					if (s == null) {
						log.warn("Expected a video object, got: " + s + " (asd_gen)");
					} else {
						final UniJsonValue stream = UniJsonValue.object();
						stream.add("itag", s.getTag());
						if (Arguments.hasArgument("--NTubeServer.exposeIPs")) {
							stream.add("url", s.videoURL());
						}
						stream.add("proxied_url", urlizer(s.videoURL()));
						stream.add("format_bitrate", s.getFormat().getBitrate());
						stream.add("codec", VideoPageUtil.getAudioCodecType(s.getFormat().getAudioCodec()));
						stream.add("surroundAudio", s.getFormat().isHighAudioChannel());
						stream.add("init", s.getInitPayload());
						stream.add("index", s.getIndexPayload());
						stream.add("bitrate", s.getBitrate());
						stream.add("size", s.getFileSize());
						if (s.hasCodecMime()) {
							stream.add("mime", s.getCodecMime());
							stream.add("pmime", s.getPrettyMime());
						} else {
							stream.add("mime", "unknown");
							stream.add("pmime", "unknown");
						}
						final UniJsonValue ecd = UniJsonValue.object();
						ecd.add("video", s.getEnhancedVideoCodec());
						ecd.add("audio", s.getEnhancedAudioCodec());
						stream.add("ecd", ecd);
						if (s.getFormat().hasDRMFamilies()) {
							final UniJsonValue d = UniJsonValue.array();
							for (final DRMFamily f : s.getFormat().getDRMFamilies())
								d.add(f.name());
							stream.add("drmFamilies", d);
						}
						audio.add(stream);
					}
				}
				return audio;
			});
			final Executable<UniJsonValue> cmbs_gen = td.asyncSubmit(() -> {
				final UniJsonValue comb = UniJsonValue.array();
				for (final YouTubeVideo s : combinedStreams) {
					if (s == null) {
						log.warn("Expected a video object, got: " + s + " (cmbs_gen)");
					} else {
						final UniJsonValue stream = UniJsonValue.object();
						stream.add("itag", s.getTag());
						if (s.videoURL() == null) {
							log.warn("Video URL was null! [itag=" + s.getTag() + ",size=" + s.getSize() + "]");
							continue;
						}
						if (s.isLiveStream()) {
							stream.add("url", urlizer(s.videoURL()) + "&ythls");
							if (Arguments.hasArgument("--NTubeServer.exposeIPs")) {
								stream.add("rawURL", s.videoURL());
							}
						} else {
							if (Arguments.hasArgument("--NTubeServer.exposeIPs")) {
								stream.add("url", s.videoURL());
							}
						}
						stream.add("quality", s.getFormat().getHeight());
						try {
							stream.add("width", Integer.parseInt(Splitters.splitString(s.getSize(), "x")[0]));
						} catch (Exception e) {
							log.error("Failed to add width!", e);
						}
						stream.add("fps", s.getFormat().getFPS());
						stream.add("codec", VideoPageUtil.getVideoCodecType(s.getFormat().getVideoCodec()));
						stream.add("liveStream", s.isLiveStream());
						stream.add("true_fps", s.getFPS());
						if (s.getFormat().hasDRMFamilies()) {
							final UniJsonValue d = UniJsonValue.array();
							for (final DRMFamily f : s.getFormat().getDRMFamilies())
								d.add(f.name());
							stream.add("drmFamilies", d);
						}
						if (s.getInitPayload() == null) {
							if (s.requiresSupplimentaryVideoAudio()) {
								log.warn(s.getTag() + ": initPayload is null! [got: " + s.getInitPayload() + "]");
							}
						} else {
							stream.add("init", s.getInitPayload());
						}
						if (s.getIndexPayload() == null) {
							if (s.requiresSupplimentaryVideoAudio()) {
								log.warn(s.getTag() + ": indexPayload is null! [got: " + s.getIndexPayload() + "]");
							}
						} else {
							stream.add("init", s.getIndexPayload());
						}
						final UniJsonValue ecd = UniJsonValue.object();
						ecd.add("video", s.getEnhancedVideoCodec());
						ecd.add("audio", s.getEnhancedAudioCodec());
						stream.add("ecd", ecd);
						stream.add("bitrate", s.getBitrate());
						stream.add("size", s.getFileSize());
						stream.add("isHDR", s.getFormat().isHDR());
						if (s.isLiveStream()) {
							stream.add("proxied_url", urlizer(s.videoURL()) + "&ythls");
						} else {
							stream.add("proxied_url", urlizer(s.videoURL()));
						}
						if (s.hasCodecMime()) {
							stream.add("mime", s.getCodecMime());
							stream.add("pmime", s.getPrettyMime());
						}
						comb.add(stream);
					}
				}
				return comb;
			});
			final Executable<UniJsonValue> vsd_gen = td.asyncSubmit(() -> {
				final UniJsonValue video = UniJsonValue.array();
				try {
					for (final YouTubeVideo s : videoStreams) {
						if (s == null) {
							log.warn("Expected a video object, got: " + s + " (vsd_gen)");
						} else {
							final UniJsonValue stream = UniJsonValue.object();
							stream.add("itag", s.getTag());
							if (Arguments.hasArgument("--NTubeServer.exposeIPs")) {
								stream.add("url", s.videoURL());
							}
							stream.add("quality", s.getFormat().getHeight());
							try {
								stream.add("width", Integer.parseInt(Splitters.splitString(s.getSize(), "x")[0]));
							} catch (Exception e) {
								log.error("Failed to add width!", e);
							}
							stream.add("fps", s.getFormat().getFPS());
							stream.add("codec", VideoPageUtil.getVideoCodecType(s.getFormat().getVideoCodec()));
							stream.add("init", s.getInitPayload());
							stream.add("index", s.getIndexPayload());
							stream.add("bitrate", s.getBitrate());
							stream.add("size", s.getFileSize());
							stream.add("true_fps", s.getFPS());
							if (!audioStreams.isEmpty()) {
								stream.add("audioPair", audioStreams.size() - 1);
							}
							if (s.hasCodecMime()) {
								stream.add("mime", s.getCodecMime());
								stream.add("pmime", s.getPrettyMime());
							}
							if (s.getFormat().hasDRMFamilies()) {
								final UniJsonValue d = UniJsonValue.array();
								for (final DRMFamily f : s.getFormat().getDRMFamilies())
									d.add(f.name());
								stream.add("drmFamilies", d);
							}
							final UniJsonValue ecd = UniJsonValue.object();
							ecd.add("video", s.getEnhancedVideoCodec());
							ecd.add("audio", s.getEnhancedAudioCodec());
							stream.add("ecd", ecd);
							stream.add("proxied_url", urlizer(s.videoURL()));
							stream.add("isHDR", s.getFormat().isHDR());
							stream.add("projectionType", s.getProjectionMode().getID());
							video.add(stream);
						}
					}
				} catch (Exception e) {
					log.error(e);
				}
				return video;
			});
			td.await(asd_gen, cmbs_gen, vsd_gen, metadata_gen);
			final UniJsonValue newVideo = UniJsonValue.object();
			newVideo.add("audioStreams", asd_gen.waitTillCompletion());
			newVideo.add("combinedStreams", cmbs_gen.waitTillCompletion());
			newVideo.add("videoStreams", vsd_gen.waitTillCompletion());
			out.add("videos", newVideo);
		}
		outv.add("result", out);
		switch (compressWith) {
			case HEATSHRINK: {
				final String comp_input = outv.toString();
				final byte[] compressed = new HeatShrinkEncoder().encode(comp_input);
				outv.add("encodedAs", "heatshrink");
				session.sendEvent(SSEvent.bldr().retry(100).data(UniBase64.encodeFromBytes(compressed)).event("video").build());
				break;
			}
			case SNAPPY: {
				final String comp_input = outv.toString();
				final SnappyCompressor sc = new SnappyCompressor();
				final byte[] input = comp_input.getBytes();
				final byte[] output = new byte[sc.maxCompressedLength(input.length)];
				sc.compress(input, output);
				outv.add("encodedAs", "snappy");
				session.sendEvent(SSEvent.bldr().retry(100).data(UniBase64.encodeFromBytes(output)).event("video").build());
				break;
			}
			case STANDARD:
			default: {
				outv.add("encodedAs", "standard");
				session.sendEvent(SSEvent.bldr().retry(100).data(outv.toString()).event("video").build());
				break;
			}
		}
	}

	@Override
	public void sessionDisconnected(final SSESession sseSession) {
		this.sessions.values().remove(sseSession);
	}

}
